package com.db.williamchart.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.util.AttributeSet;
import com.db.williamchart.extensions.Pair;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.OneShotPreDrawListener;

import com.db.williamchart.ChartContract;
import com.db.williamchart.ExperimentalFeature;
import com.db.williamchart.Grid;
import com.db.williamchart.Labels;
import com.db.williamchart.Painter;
import com.db.williamchart.R;
import com.db.williamchart.Tooltip;
import com.db.williamchart.animation.ChartAnimation;
import com.db.williamchart.animation.DefaultAnimation;
import com.db.williamchart.data.AxisType;
import com.db.williamchart.data.DataPoint;
import com.db.williamchart.data.Frame;
import com.db.williamchart.data.Scale;
import com.db.williamchart.data.configuration.ChartConfiguration;
import com.db.williamchart.plugin.AxisGrid;
import com.db.williamchart.plugin.AxisLabels;
import com.db.williamchart.plugin.GridEffect;
import com.db.williamchart.plugin.GridType;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;

import kotlin.OptIn;
import kotlin.Triple;

import static com.db.williamchart.renderer.RendererConstants.notInitialized;

@OptIn(markerClass = ExperimentalFeature.class)
public abstract class AxisChartView extends FrameLayout {

    private LinkedHashMap<String, Float> editModeSampleData = new LinkedHashMap<>();

    private static final int defaultFrameWidth = 200;
    private static final int defaultFrameHeight = 100;
    private static final float defaultLabelsSize = 60F;
    protected float labelsSize = defaultLabelsSize;
    protected int labelsColor = -0x1000000;
    protected Typeface labelsFont = null;
    protected AxisType axis = AxisType.XY;
    protected Scale scale = new Scale(notInitialized, notInitialized);
    protected float labelsFormatter;

    protected ChartAnimation<DataPoint> animation = new DefaultAnimation();
    protected Labels labels = new AxisLabels();

    private Tooltip tooltip = new Tooltip() {
        @Override
        public void onCreateTooltip(ViewGroup parentView) {
        }

        @Override
        public void onDataPointTouch(float x, float y) {
        }

        @Override
        public void onDataPointClick(float x, float y) {
        }
    };

    @ExperimentalFeature
    protected Grid grid = new Grid() {
        @Override
        public void draw(Canvas canvas, Frame innerFrame, List<Float> xLabelsPositions, List<Float> yLabelsPositions) {

        }
    };

    @ExperimentalFeature
    private OnDataPointClickListener onDataPointClickListener;

    @ExperimentalFeature
    private OnDataPointTouchListener onDataPointTouchListener;

    public void setOnDataPointClickListener(OnDataPointClickListener onDataPointClickListener) {
        this.onDataPointClickListener = onDataPointClickListener;
    }

    public void setOnDataPointTouchListener(OnDataPointTouchListener onDataPointTouchListener) {
        this.onDataPointTouchListener = onDataPointTouchListener;
    }

    protected Canvas canvas;

    protected Painter painter = new Painter(labelsFont);

    protected ChartContract.Renderer renderer;

    private final GestureDetectorCompat gestureDetector = new GestureDetectorCompat(getContext(), new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public void onShowPress(MotionEvent e) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Triple<Integer, Float, Float> triple = renderer.processClick(e.getX(), e.getY());
            if (triple.component1() != -1) {
                if (onDataPointClickListener != null){
                    onDataPointClickListener.onPointClick(triple.component1(), triple.component2(), triple.component3());
                }
                tooltip.onDataPointClick(triple.component2(), triple.component3());
                return true;
            }
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {

        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }
    });

    public AxisChartView(@NonNull Context context) {
        this(context,null);
    }

    public AxisChartView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AxisChartView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    @SuppressLint("CustomViewStyleable")
    private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
        editModeSampleData.put("Label1",1f);
        editModeSampleData.put("Label2",7.5f);
        editModeSampleData.put("Label3",4.7f);
        editModeSampleData.put("Label4",3.5f);
        handleAttributes(context.obtainStyledAttributes(attrs, R.styleable.ChartAttrs));
//        final ViewTreeObserver observer = getViewTreeObserver();
//        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
//            @Override
//            public boolean onPreDraw() {
//                AxisChartView.this.getViewTreeObserver().removeOnPreDrawListener(this);
//                tooltip.onCreateTooltip(AxisChartView.this);
//                return false;
//            }
//        });
        OneShotPreDrawListener.add(this, new Runnable() {
            @Override
            public void run() {
                tooltip.onCreateTooltip(AxisChartView.this);
            }
        });
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        setWillNotDraw(false);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        setMeasuredDimension(
                widthMode == MeasureSpec.AT_MOST ? defaultFrameWidth : widthMeasureSpec,
                heightMode == MeasureSpec.AT_MOST ? defaultFrameHeight : heightMeasureSpec
        );
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.canvas = canvas;
        renderer.draw();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Triple<Integer, Float, Float> triple = renderer.processTouch(event.getX(), event.getY());
        if (triple.component1() != -1) {
            if (onDataPointTouchListener != null){
                onDataPointTouchListener.onPointTouch(triple.component1(), triple.component2(), triple.component3());
            }
            tooltip.onDataPointTouch(triple.component2(), triple.component3());
        }
        if (gestureDetector.onTouchEvent(event)) return true;
        else return super.onTouchEvent(event);
    }

    abstract ChartConfiguration getChartConfiguration();

    @Deprecated
    public void show(LinkedHashMap<String, Float> entries) {
//        final ViewTreeObserver observer = getViewTreeObserver();
//        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
//            @Override
//            public boolean onPreDraw() {
//                observer.removeOnPreDrawListener(this);
//                try {
//                    renderer.preDraw(getChartConfiguration());
//                } catch (IllegalAccessException e) {
//                    e.printStackTrace();
//                }
//                return false;
//            }
//        });

        OneShotPreDrawListener.add(this, new Runnable() {
            @Override
            public void run() {
                try {
                    renderer.preDraw(getChartConfiguration());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });

        List<Pair<String, Float>> pairList = mapToList(entries);
        renderer.render(pairList);
    }

    @NotNull
    private List<Pair<String, Float>> mapToList(LinkedHashMap<String, Float> entries) {
        Iterator<String> iterator = entries.keySet().iterator();
        List<Pair<String, Float>> pairList = new ArrayList<>();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Float value = entries.get(key);
            pairList.add(new Pair<String, Float>(key, value));
        }
        return pairList;
    }

    @Deprecated
    public void animate(LinkedHashMap<String, Float> entries) {
//        final ViewTreeObserver observer = getViewTreeObserver();
//        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
//            @Override
//            public boolean onPreDraw() {
//                observer.removeOnPreDrawListener(this);
//                try {
//                    renderer.preDraw(getChartConfiguration());
//                } catch (IllegalAccessException e) {
//                    e.printStackTrace();
//                }
//                return false;
//            }
//        });
        OneShotPreDrawListener.add(this, new Runnable() {
            @Override
            public void run() {
                try {
                    renderer.preDraw(getChartConfiguration());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });
        renderer.anim(mapToList(entries), animation);
    }

    public void show(List<Pair<String, Float>> entries) {
//        final ViewTreeObserver observer = getViewTreeObserver();
//        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
//            @Override
//            public boolean onPreDraw() {
//                observer.removeOnPreDrawListener(this);
//                try {
//                    renderer.preDraw(getChartConfiguration());
//                } catch (IllegalAccessException e) {
//                    e.printStackTrace();
//                }
//                return false;
//            }
//        });
        OneShotPreDrawListener.add(this, new Runnable() {
            @Override
            public void run() {
                try {
                    renderer.preDraw(getChartConfiguration());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });
        renderer.render(entries);
    }

    public void animate(List<Pair<String, Float>> entries) {

        OneShotPreDrawListener.add(this, new Runnable() {
            @Override
            public void run() {
                try {
                    renderer.preDraw(getChartConfiguration());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });

        renderer.anim(entries, animation);
    }

    private void handleAttributes(TypedArray typedArray) {
        switch (typedArray.getString(R.styleable.ChartAttrs_chart_axis)) {
            case "0":
                axis = AxisType.NONE;
                break;
            case "1":
                axis = AxisType.X;
                break;
            case "2":
                axis = AxisType.Y;
                break;
            default:
                axis = AxisType.XY;
                break;
        }

        labelsSize = typedArray.getDimension(R.styleable.ChartAttrs_chart_labelsSize, labelsSize);
        labelsColor = typedArray.getColor(R.styleable.ChartAttrs_chart_labelsColor, labelsColor);
        if (typedArray.hasValue(R.styleable.ChartAttrs_chart_labelsFont) && !isInEditMode()) {
            labelsFont =
                    ResourcesCompat.getFont(
                            getContext(),
                            typedArray.getResourceId(R.styleable.ChartAttrs_chart_labelsFont, -1)
                    );
            painter.setLabelsFont(labelsFont);
        }

        // Customize Grid
        if (typedArray.hasValue(R.styleable.ChartAttrs_chart_grid)) {
            AxisGrid axisGrid = new AxisGrid();
            switch (typedArray.getString(R.styleable.ChartAttrs_chart_grid)) {
                case "1":
                    axisGrid.setGridType(GridType.VERTICAL);
                    break;
                case "2":
                    axisGrid.setGridType(GridType.HORIZONTAL);
                    break;
                default:
                    axisGrid.setGridType(GridType.FULL);
                    break;
            }
            axisGrid.setColor(typedArray.getColor(R.styleable.ChartAttrs_chart_gridColor, axisGrid.getColor()));
            axisGrid.setStrokeWidth(typedArray.getDimension(R.styleable.ChartAttrs_chart_gridStrokeWidth, axisGrid.getStrokeWidth()));
            switch (typedArray.getString(R.styleable.ChartAttrs_chart_gridEffect)) {
                case "1":
                    axisGrid.setGridEffect(GridEffect.DASHED);
                    break;
                case "2":
                    axisGrid.setGridEffect(GridEffect.DOTTED);
                    break;
                default:
                    axisGrid.setGridEffect(GridEffect.SOLID);
                    break;
            }

            grid = axisGrid;
            typedArray.recycle();
        }
    }

    protected void handleEditMode() {
        if (isInEditMode()) {
            show(editModeSampleData);
        }
    }

    public interface OnDataPointClickListener {
        void onPointClick(int index, float x, float y);
    }

    public interface OnDataPointTouchListener {
        void onPointTouch(int index, float x, float y);
    }

    public Tooltip getTooltip() {
        return tooltip;
    }

    public void setTooltip(Tooltip tooltip) {
        this.tooltip = tooltip;
    }

    public ChartAnimation<DataPoint> getChartAnimation() {
        return animation;
    }
}
